<script setup>
import { ref, onMounted } from 'vue'
import { get, post, put, del, uploadFile, downloadFile } from '../utils/request'

// 状态变量
const users = ref([])
const loading = ref(false)
const error = ref(null)
const message = ref('')
const messageType = ref('success')
const showMessage = ref(false)

// 新用户表单
const newUser = ref({
  name: '',
  email: '',
  avatar: null
})

// 获取用户列表
async function fetchUsers() {
  loading.value = true
  error.value = null
  
  try {
    const response = await get('/users')
    users.value = response
    showNotification('获取用户列表成功', 'success')
  } catch (err) {
    error.value = err.message
    showNotification('获取用户列表失败', 'error')
  } finally {
    loading.value = false
  }
}

// 创建用户
async function createUser() {
  if (!newUser.value.name || !newUser.value.email) {
    showNotification('请填写完整的用户信息', 'error')
    return
  }
  
  loading.value = true
  
  try {
    const formData = new FormData()
    formData.append('name', newUser.value.name)
    formData.append('email', newUser.value.email)
    if (newUser.value.avatar) {
      formData.append('avatar', newUser.value.avatar)
    }
    
    await post('/users', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
    
    showNotification('创建用户成功', 'success')
    resetForm()
    fetchUsers()
  } catch (err) {
    showNotification('创建用户失败', 'error')
  } finally {
    loading.value = false
  }
}

// 其他方法
async function updateUser(userId, userData) {
  loading.value = true
  try {
    await put(`/users/${userId}`, userData)
    showNotification('更新用户成功', 'success')
    fetchUsers()
  } catch (err) {
    showNotification('更新用户失败', 'error')
  } finally {
    loading.value = false
  }
}

async function deleteUser(userId) {
  if (!confirm('确定要删除此用户吗？')) return
  
  loading.value = true
  try {
    await del(`/users/${userId}`)
    showNotification('删除用户成功', 'success')
    fetchUsers()
  } catch (err) {
    showNotification('删除用户失败', 'error')
  } finally {
    loading.value = false
  }
}

// 辅助方法
function resetForm() {
  newUser.value = { name: '', email: '', avatar: null }
}

function showNotification(msg, type = 'success') {
  message.value = msg
  messageType.value = type
  showMessage.value = true
  
  setTimeout(() => {
    showMessage.value = false
  }, 3000)
}

onMounted(() => {
  fetchUsers()
})
</script>

<template>
  <div class="tutorial-container">
    <header>
      <h1>Axios 完整指南</h1>
      <p class="subtitle">深入学习Axios在Vue项目中的封装与使用</p>
    </header>

    <main>
      <!-- 目录部分 -->
      <div class="toc-section">
        <h2>目录</h2>
        <ul class="toc-list">
          <li><a href="#intro">1. Axios简介</a></li>
          <li><a href="#encapsulation">2. Axios封装详解</a></li>
          <li><a href="#interceptors">3. 拦截器设计</a></li>
          <li><a href="#error-handling">4. 错误处理策略</a></li>
          <li><a href="#examples">5. 实战案例</a></li>
          <li><a href="#best-practices">6. 最佳实践</a></li>
        </ul>
      </div>
      
      <!-- Axios简介 -->
      <section id="intro" class="content-section">
        <h2>1. Axios简介</h2>
        
        <div class="subsection">
          <h3>1.1 什么是Axios</h3>
          <p>Axios是一个基于Promise的HTTP客户端，可以用于浏览器和Node.js环境。它具有以下主要特点：</p>
          <ul>
            <li>支持Promise API，让异步请求更加优雅</li>
            <li>可以拦截请求和响应，统一处理请求和错误</li>
            <li>自动转换JSON数据，简化数据处理</li>
            <li>支持取消请求和请求超时设置</li>
            <li>客户端支持防御XSRF攻击</li>
            <li>支持浏览器和Node.js环境，实现前后端代码复用</li>
          </ul>
        </div>
        
        <div class="subsection">
          <h3>1.2 为什么需要封装Axios</h3>
          <p>在实际项目中，直接使用Axios存在以下问题：</p>
          <ul>
            <li><strong>代码重复</strong>：每个请求都需要处理loading、错误等状态</li>
            <li><strong>配置分散</strong>：baseURL、timeout等配置散落在各处</li>
            <li><strong>错误处理不统一</strong>：每个请求都需要单独捕获和处理错误</li>
            <li><strong>缺乏扩展性</strong>：难以添加统一的鉴权、日志等功能</li>
            <li><strong>维护成本高</strong>：接口变更需要修改多处代码</li>
          </ul>
          <p>因此，我们需要对Axios进行封装，实现以下目标：</p>
          <ul>
            <li>统一配置和管理请求参数</li>
            <li>统一处理请求和响应</li>
            <li>统一处理错误和异常</li>
            <li>简化API调用方式</li>
            <li>提高代码可维护性和可扩展性</li>
          </ul>
        </div>
      </section>
      
      <!-- Axios封装详解 -->
      <section id="encapsulation" class="content-section">
        <h2>2. Axios封装详解</h2>
        
        <div class="subsection">
          <h3>2.1 基础封装思路</h3>
          <p>封装Axios的基本思路包括以下几个步骤：</p>
          <ol>
            <li>创建Axios实例，设置基础配置</li>
            <li>添加请求拦截器，处理请求前的统一逻辑</li>
            <li>添加响应拦截器，处理响应数据和错误</li>
            <li>封装常用的请求方法（GET、POST、PUT、DELETE等）</li>
            <li>实现请求工具函数，如文件上传下载等</li>
          </ol>
        </div>
        
        <div class="subsection">
          <h3>2.2 request.js详细解析</h3>
          <p>下面是一个完整的request.js文件结构，我们将逐一解析其中的关键部分：</p>
          
          <div class="code-example">
            <pre><code>// src/utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'

// 1. 创建axios实例
const service = axios.create({
  // 基础URL，可以从环境变量中获取
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  // 请求超时时间
  timeout: 15000,
  // 请求头
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
})

// 2. 请求拦截器
service.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么
    // 例如：添加token、处理请求参数等
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    
    // 添加时间戳，防止GET请求缓存
    if (config.method === 'get') {
      config.params = {
        ...config.params,
        _t: Date.now()
      }
    }
    
    // 这里可以添加loading状态管理
    // showLoading()
    
    return config
  },
  error => {
    // 对请求错误做些什么
    console.error('请求错误：', error)
    return Promise.reject(error)
  }
)

// 3. 响应拦截器
service.interceptors.response.use(
  response => {
    // 对响应数据做点什么
    const res = response.data
    
    // 自定义状态码处理
    // 假设后端返回的数据格式为 { code: number, data: any, message: string }
    if (res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      
      // 处理特定状态码
      if (res.code === 401) {
        // token过期，清除token并跳转到登录页
        localStorage.removeItem('token')
        window.location.href = '/login'
        return Promise.reject(new Error('登录已过期，请重新登录'))
      }
      
      return Promise.reject(new Error(res.message || '未知错误'))
    }
    
    // 隐藏loading
    // hideLoading()
    
    // 只返回数据部分
    return res.data
  },
  error => {
    // 对响应错误做点什么
    // hideLoading()
    
    // 处理HTTP错误状态码
    if (error.response) {
      switch (error.response.status) {
        case 401:
          ElMessage.error('未授权，请重新登录')
          localStorage.removeItem('token')
          window.location.href = '/login'
          break
        case 403:
          ElMessage.error('拒绝访问')
          break
        case 404:
          ElMessage.error('请求的资源不存在')
          break
        case 500:
          ElMessage.error('服务器内部错误')
          break
        default:
          ElMessage.error(`请求失败: ${error.response.status}`)
      }
    } else if (error.request) {
      ElMessage.error('服务器未响应')
    } else {
      ElMessage.error('请求配置错误')
    }
    
    return Promise.reject(error)
  }
)

// 4. 封装GET请求
export function get(url, params, config = {}) {
  return service({
    url,
    method: 'get',
    params,
    ...config
  })
}

// 5. 封装POST请求
export function post(url, data, config = {}) {
  return service({
    url,
    method: 'post',
    data,
    ...config
  })
}

// 6. 封装PUT请求
export function put(url, data, config = {}) {
  return service({
    url,
    method: 'put',
    data,
    ...config
  })
}

// 7. 封装DELETE请求
export function del(url, params, config = {}) {
  return service({
    url,
    method: 'delete',
    params,
    ...config
  })
}

// 8. 封装上传文件的POST请求
export function uploadFile(url, file, config = {}) {
  const formData = new FormData()
  formData.append('file', file)
  
  return service({
    url,
    method: 'post',
    data: formData,
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    ...config
  })
}

// 9. 封装下载文件的GET请求
export function downloadFile(url, params, filename, config = {}) {
  return service({
    url,
    method: 'get',
    params,
    responseType: 'blob',
    ...config
  }).then(blob => {
    const link = document.createElement('a')
    const objectUrl = URL.createObjectURL(blob)
    link.href = objectUrl
    link.download = filename
    link.click()
    URL.revokeObjectURL(objectUrl)
  })
}

export default service</code></pre>
          </div>
          
          <p>上面的代码主要包含以下几个部分：</p>
          <ol>
            <li><strong>创建Axios实例</strong>：配置baseURL、timeout和默认请求头</li>
            <li><strong>请求拦截器</strong>：添加token、防止缓存、管理loading状态</li>
            <li><strong>响应拦截器</strong>：统一处理业务错误和HTTP错误</li>
            <li><strong>请求方法封装</strong>：简化GET、POST等请求方法的调用</li>
            <li><strong>文件处理</strong>：封装文件上传和下载功能</li>
          </ol>
        </div>
        
        <div class="subsection">
          <h3>2.3 实例配置详解</h3>
          <p>创建Axios实例时，我们可以配置以下常用选项：</p>
          <ul>
            <li><strong>baseURL</strong>：基础URL，所有请求的前缀</li>
            <li><strong>timeout</strong>：请求超时时间，单位毫秒</li>
            <li><strong>headers</strong>：默认请求头</li>
            <li><strong>withCredentials</strong>：是否携带cookie信息</li>
            <li><strong>responseType</strong>：响应数据类型，如json、blob等</li>
            <li><strong>maxContentLength</strong>：响应内容最大长度</li>
            <li><strong>maxBodyLength</strong>：请求体最大长度</li>
          </ul>
          <p>根据项目需求，可以灵活配置这些选项。特别是baseURL，可以根据不同的环境（开发、测试、生产）从环境变量中获取，实现环境切换。</p>
        </div>
      </section>
      
      <!-- 拦截器设计 -->
      <section id="interceptors" class="content-section">
        <h2>3. 拦截器设计</h2>
        
        <div class="subsection">
          <h3>3.1 请求拦截器</h3>
          <p>请求拦截器可以在请求发送前对请求进行处理，常见用途包括：</p>
          
          <h4>3.1.1 添加认证信息</h4>
          <p>大多数API都需要认证，通常是通过添加token到请求头实现：</p>
          <div class="code-example">
            <pre><code>// 从localStorage中获取token并添加到请求头
service.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`
  }
  return config
})</code></pre>
          </div>
          
          <h4>3.1.2 请求参数处理</h4>
          <p>对请求参数进行预处理，如添加公共参数、序列化等：</p>
          <div class="code-example">
            <pre><code>// 为GET请求添加时间戳，防止缓存
service.interceptors.request.use(config => {
  if (config.method === 'get') {
    config.params = {
      ...config.params,
      _t: Date.now()
    }
  }
  return config
})</code></pre>
          </div>
          
          <h4>3.1.3 加载状态管理</h4>
          <p>统一控制加载状态，避免在每个请求中重复处理：</p>
          <div class="code-example">
            <pre><code>// 全局loading计数器
let loadingCount = 0
const showLoading = () => {
  loadingCount++
  if (loadingCount === 1) {
    // 显示全局loading
    // ElLoading.service()
  }
}

const hideLoading = () => {
  loadingCount--
  if (loadingCount === 0) {
    // 隐藏全局loading
    // loading.close()
  }
}

// 在请求拦截器中使用
service.interceptors.request.use(config => {
  // 显示loading
  if (config.showLoading !== false) {
    showLoading()
  }
  return config
})</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>3.2 响应拦截器</h3>
          <p>响应拦截器可以对服务器返回的数据进行统一处理，常见用途包括：</p>
          
          <h4>3.2.1 数据转换与过滤</h4>
          <p>简化响应数据结构，只返回业务所需的部分：</p>
          <div class="code-example">
            <pre><code>// 假设后端返回的数据格式为 { code: number, data: any, message: string }
service.interceptors.response.use(response => {
  const res = response.data
  
  // 成功状态下，只返回data部分
  if (res.code === 200) {
    return res.data
  }
  
  // 其他情况统一处理
  return Promise.reject(new Error(res.message || '未知错误'))
})</code></pre>
          </div>
          
          <h4>3.2.2 统一错误处理</h4>
          <p>根据业务状态码处理不同的错误情况：</p>
          <div class="code-example">
            <pre><code>service.interceptors.response.use(
  response => {
    const res = response.data
    
    // 业务错误处理
    if (res.code !== 200) {
      // 显示错误信息
      ElMessage.error(res.message || '操作失败')
      
      // 特殊错误码处理
      switch (res.code) {
        case 401:
          // 未登录或token过期
          localStorage.removeItem('token')
          router.push('/login')
          break
        case 403:
          // 权限不足
          router.push('/403')
          break
        // 其他业务错误码处理...
      }
      
      // 抛出错误，中断Promise链
      return Promise.reject(new Error(res.message || '未知错误'))
    }
    
    // 返回数据
    return res.data
  }
)</code></pre>
          </div>
          
          <h4>3.2.3 HTTP错误处理</h4>
          <p>处理不同HTTP状态码的错误：</p>
          <div class="code-example">
            <pre><code>service.interceptors.response.use(
  response => response.data,
  error => {
    // 处理HTTP错误
    if (error.response) {
      switch (error.response.status) {
        case 401:
          ElMessage.error('未授权，请重新登录')
          // 清除token并跳转登录页
          break
        case 403:
          ElMessage.error('拒绝访问')
          break
        case 404:
          ElMessage.error('请求的资源不存在')
          break
        case 500:
          ElMessage.error('服务器内部错误')
          break
        default:
          ElMessage.error(`请求失败: ${error.response.status}`)
      }
    } else if (error.request) {
      // 请求已发送但没有收到响应
      ElMessage.error('服务器未响应')
    } else {
      // 请求配置出错
      ElMessage.error('请求配置错误')
    }
    
    return Promise.reject(error)
  }
)</code></pre>
          </div>
        </div>
      </section>
      
      <!-- 错误处理策略 -->
      <section id="error-handling" class="content-section">
        <h2>4. 错误处理策略</h2>
        
        <div class="subsection">
          <h3>4.1 错误类型与分类</h3>
          <p>在使用Axios过程中，常见的错误类型包括：</p>
          <ul>
            <li><strong>请求配置错误</strong>：请求URL、参数格式等配置错误</li>
            <li><strong>网络错误</strong>：网络断开、请求超时等</li>
            <li><strong>HTTP错误</strong>：服务器返回4xx、5xx等状态码</li>
            <li><strong>业务错误</strong>：请求成功但业务处理失败，如权限不足、参数错误等</li>
          </ul>
        </div>
        
        <div class="subsection">
          <h3>4.2 全局错误处理</h3>
          <p>全局错误处理通常在响应拦截器中实现，可以根据错误类型进行分类处理：</p>
          <div class="code-example">
            <pre><code>// 全局错误处理函数
function handleError(error) {
  // 隐藏加载状态
  hideLoading()
  
  // 网络错误
  if (!error.response) {
    if (error.message.includes('timeout')) {
      ElMessage.error('请求超时，请检查网络')
    } else {
      ElMessage.error('网络异常，请检查网络连接')
    }
    return
  }
  
  // HTTP错误
  const status = error.response.status
  switch (status) {
    case 400:
      ElMessage.error('请求参数错误')
      break
    case 401:
      ElMessage.error('未授权，请重新登录')
      // 清除token并跳转到登录页
      localStorage.removeItem('token')
      router.push('/login')
      break
    case 403:
      ElMessage.error('拒绝访问')
      break
    case 404:
      ElMessage.error('请求的资源不存在')
      break
    case 500:
      ElMessage.error('服务器内部错误')
      break
    default:
      ElMessage.error(`请求失败: ${status}`)
  }
}

// 在响应拦截器中使用
service.interceptors.response.use(
  response => response.data,
  error => {
    handleError(error)
    return Promise.reject(error)
  }
)</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>4.3 业务错误处理</h3>
          <p>业务错误是指HTTP请求成功（状态码为200），但业务处理失败的情况。通常由后端返回特定的错误码和错误信息。</p>
          <div class="code-example">
            <pre><code>// 业务错误处理
service.interceptors.response.use(response => {
  const res = response.data
  
  // 假设后端返回格式为 { code: number, data: any, message: string }
  if (res.code !== 200) {
    // 显示错误信息
    ElMessage.error(res.message || '操作失败')
    
    // 特殊错误码处理
    switch (res.code) {
      case 401:
        // 未登录或token过期
        localStorage.removeItem('token')
        router.push('/login')
        break
      case 403:
        // 权限不足
        router.push('/403')
        break
      // 其他业务错误码处理...
    }
    
    // 抛出错误，中断Promise链
    return Promise.reject(new Error(res.message || '未知错误'))
  }
  
  // 返回数据
  return res.data
})</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>4.4 错误重试机制</h3>
          <p>对于网络波动导致的临时错误，可以实现请求重试机制：</p>
          <div class="code-example">
            <pre><code>// 封装带重试机制的请求函数
export function requestWithRetry(config, retryCount = 3, retryDelay = 1000) {
  let retries = 0
  
  const request = async () => {
    try {
      return await service(config)
    } catch (error) {
      // 只对网络错误或服务器错误进行重试
      if (
        !error.response ||
        (error.response.status >= 500 && error.response.status < 600)
      ) {
        if (retries < retryCount) {
          retries++
          // 延迟一段时间后重试
          await new Promise(resolve => setTimeout(resolve, retryDelay))
          console.log(`重试请求 (${retries}/${retryCount})`)
          return request()
        }
      }
      
      // 超过重试次数或不符合重试条件，抛出错误
      throw error
    }
  }
  
  return request()
}</code></pre>
          </div>
        </div>
      </section>
      
      <!-- 实战案例 -->
      <section id="examples" class="content-section">
        <h2>5. 实战案例</h2>
        
        <div class="subsection">
          <h3>5.1 用户管理CRUD示例</h3>
          <p>下面是一个完整的用户管理CRUD示例，展示如何使用封装的Axios进行增删改查操作：</p>
          
          <h4>5.1.1 API模块封装</h4>
          <p>首先，创建一个专门处理用户相关API的模块：</p>
          <div class="code-example">
            <pre><code>// src/api/user.js
import { get, post, put, del } from '../utils/request'

// 用户相关API接口
export default {
  // 获取用户列表
  getUsers(params) {
    return get('/users', params)
  },
  
  // 获取单个用户
  getUser(id) {
    return get(`/users/${id}`)
  },
  
  // 创建用户
  createUser(data) {
    return post('/users', data)
  },
  
  // 更新用户
  updateUser(id, data) {
    return put(`/users/${id}`, data)
  },
  
  // 删除用户
  deleteUser(id) {
    return del(`/users/${id}`)
  }
}</code></pre>
          </div>
          
          <h4>5.1.2 在Vue组件中使用</h4>
          <p>在Vue组件中使用API模块：</p>
          <div class="code-example">
            <pre><code>// 组合式API (Composition API)
import { ref, onMounted } from 'vue'
import userApi from '../api/user'

export default {
  setup() {
    // 状态
    const users = ref([])
    const loading = ref(false)
    const error = ref(null)
    
    // 获取用户列表
    async function fetchUsers() {
      loading.value = true
      error.value = null
      
      try {
        const response = await userApi.getUsers()
        users.value = response
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    }
    
    // 创建用户
    async function createUser(userData) {
      loading.value = true
      
      try {
        await userApi.createUser(userData)
        await fetchUsers() // 刷新列表
        return true
      } catch (err) {
        error.value = err.message
        return false
      } finally {
        loading.value = false
      }
    }
    
    // 更新用户
    async function updateUser(id, userData) {
      loading.value = true
      
      try {
        await userApi.updateUser(id, userData)
        await fetchUsers() // 刷新列表
        return true
      } catch (err) {
        error.value = err.message
        return false
      } finally {
        loading.value = false
      }
    }
    
    // 删除用户
    async function deleteUser(id) {
      loading.value = true
      
      try {
        await userApi.deleteUser(id)
        await fetchUsers() // 刷新列表
        return true
      } catch (err) {
        error.value = err.message
        return false
      } finally {
        loading.value = false
      }
    }
    
    // 页面加载时获取数据
    onMounted(fetchUsers)
    
    return {
      users,
      loading,
      error,
      fetchUsers,
      createUser,
      updateUser,
      deleteUser
    }
  }
}</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>5.2 文件上传下载示例</h3>
          
          <h4>5.2.1 文件上传</h4>
          <p>文件上传是常见的场景，下面展示如何使用封装的uploadFile方法：</p>
          <div class="code-example">
            <pre><code>import { ref } from 'vue'
import { uploadFile } from '../utils/request'

export default {
  setup() {
    const uploadProgress = ref(0)
    const uploadStatus = ref('')
    
    async function handleFileUpload(event) {
      const file = event.target.files[0]
      if (!file) return
      
      try {
        uploadStatus.value = 'uploading'
        
        // 使用uploadFile方法，添加进度监听
        const response = await uploadFile('/upload', file, {
          onUploadProgress: progressEvent => {
            uploadProgress.value = Math.round(
              (progressEvent.loaded * 100) / progressEvent.total
            )
          }
        })
        
        uploadStatus.value = 'success'
        return response
      } catch (error) {
        uploadStatus.value = 'error'
        console.error('上传失败:', error)
      }
    }
    
    return {
      uploadProgress,
      uploadStatus,
      handleFileUpload
    }
  }
}</code></pre>
          </div>
          
          <h4>5.2.2 文件下载</h4>
          <p>文件下载也是常见场景，下面展示如何使用封装的downloadFile方法：</p>
          <div class="code-example">
            <pre><code>import { downloadFile } from '../utils/request'

export default {
  setup() {
    async function handleDownload(fileId, filename) {
      try {
        // 调用下载方法
        await downloadFile('/download', { fileId }, filename)
        console.log('下载成功')
      } catch (error) {
        console.error('下载失败:', error)
      }
    }
    
    return {
      handleDownload
    }
  }
}</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>5.3 与Pinia集成</h3>
          <p>Pinia是Vue 3推荐的状态管理库，可以与Axios结合使用，实现更优雅的数据管理：</p>
          <div class="code-example">
            <pre><code>// src/stores/user.js
import { defineStore } from 'pinia'
import userApi from '../api/user'

export const useUserStore = defineStore('user', {
  state: () => ({
    users: [],
    currentUser: null,
    loading: false,
    error: null
  }),
  
  getters: {
    getUserById: (state) => (id) => {
      return state.users.find(user => user.id === id)
    }
  },
  
  actions: {
    // 获取用户列表
    async fetchUsers() {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.getUsers()
        this.users = response
      } catch (error) {
        this.error = error.message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    // 获取单个用户
    async fetchUserById(id) {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.getUser(id)
        this.currentUser = response
        return response
      } catch (error) {
        this.error = error.message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    // 创建用户
    async createUser(userData) {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.createUser(userData)
        await this.fetchUsers() // 刷新列表
        return response
      } catch (error) {
        this.error = error.message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    // 更新用户
    async updateUser(id, userData) {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.updateUser(id, userData)
        
        // 更新本地状态
        const index = this.users.findIndex(user => user.id === id)
        if (index !== -1) {
          this.users[index] = { ...this.users[index], ...userData }
        }
        
        if (this.currentUser && this.currentUser.id === id) {
          this.currentUser = { ...this.currentUser, ...userData }
        }
        
        return response
      } catch (error) {
        this.error = error.message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    // 删除用户
    async deleteUser(id) {
      this.loading = true
      this.error = null
      
      try {
        await userApi.deleteUser(id)
        
        // 更新本地状态
        this.users = this.users.filter(user => user.id !== id)
        
        if (this.currentUser && this.currentUser.id === id) {
          this.currentUser = null
        }
      } catch (error) {
        this.error = error.message
        throw error
      } finally {
        this.loading = false
      }
    },
    
    // 重置状态
    resetState() {
      this.users = []
      this.currentUser = null
      this.loading = false
      this.error = null
    }
  }
})</code></pre>
          </div>
          
          <p>在Vue组件中使用Pinia store：</p>
          <div class="code-example">
            <pre><code>// Composition API
import { storeToRefs } from 'pinia'
import { useUserStore } from '../stores/user'

export default {
  setup() {
    const userStore = useUserStore()
    
    // 使用storeToRefs保持响应性
    const { users, currentUser, loading, error } = storeToRefs(userStore)
    
    // 组件挂载时获取数据
    onMounted(async () => {
      try {
        await userStore.fetchUsers()
      } catch (err) {
        console.error('获取用户列表失败:', err)
      }
    })
    
    return {
      users,
      currentUser,
      loading,
      error,
      // 直接使用store的actions
      fetchUserById: userStore.fetchUserById,
      createUser: userStore.createUser,
      updateUser: userStore.updateUser,
      deleteUser: userStore.deleteUser
    }
  }
}</code></pre>
          </div>
        </div>
      </section>
      
      <!-- 最佳实践 -->
      <section id="best-practices" class="content-section">
        <h2>6. 最佳实践</h2>
        
        <div class="subsection">
          <h3>6.1 封装技巧</h3>
          
          <h4>6.1.1 按照业务模块划分API</h4>
          <p>将API按业务模块划分，便于管理和维护：</p>
          <div class="code-example">
            <pre><code>// src/api/index.js
import user from './user'
import product from './product'
import order from './order'

export default {
  user,
  product,
  order
}

// 使用方式
import api from '@/api'
api.user.getUsers()
api.product.getProducts()
api.order.createOrder(orderData)</code></pre>
          </div>
          
          <h4>6.1.2 使用配置文件管理API地址</h4>
          <p>将API路径统一管理，便于维护：</p>
          <div class="code-example">
            <pre><code>// src/api/urls.js
export default {
  user: {
    list: '/users',
    detail: '/users/:id',
    create: '/users',
    update: '/users/:id',
    delete: '/users/:id'
  },
  product: {
    // 产品相关API...
  }
}

// 使用方式
import urls from '@/api/urls'
import { get, post } from '@/utils/request'

export function getUser(id) {
  return get(urls.user.detail.replace(':id', id))
}</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>6.2 性能优化</h3>
          
          <h4>6.2.1 请求缓存</h4>
          <p>对于不常变化的数据，可以实现请求缓存，避免重复请求：</p>
          <div class="code-example">
            <pre><code>// 简单的请求缓存实现
const cacheMap = new Map()

export function cachedRequest(url, params, config = {}, expireTime = 60000) {
  const cacheKey = url + JSON.stringify(params)
  
  // 检查缓存
  const cached = cacheMap.get(cacheKey)
  if (cached && Date.now() - cached.timestamp < expireTime) {
    return Promise.resolve(cached.data)
  }
  
  // 没有缓存或缓存过期，发起请求
  return get(url, params, config).then(data => {
    // 存入缓存
    cacheMap.set(cacheKey, {
      data,
      timestamp: Date.now()
    })
    return data
  })
}</code></pre>
          </div>
          
          <h4>6.2.2 请求合并</h4>
          <p>对于短时间内的重复请求，可以实现请求合并，减少服务器压力：</p>
          <div class="code-example">
            <pre><code>// 请求合并
const pendingRequests = new Map()

export function mergedRequest(url, params, config = {}) {
  const requestKey = url + JSON.stringify(params)
  
  // 检查是否有相同的请求正在进行
  if (pendingRequests.has(requestKey)) {
    // 返回已经在进行的请求
    return pendingRequests.get(requestKey)
  }
  
  // 发起新请求
  const request = get(url, params, config).finally(() => {
    // 请求完成后从Map中删除
    pendingRequests.delete(requestKey)
  })
  
  // 将请求存入Map
  pendingRequests.set(requestKey, request)
  
  return request
}</code></pre>
          </div>
          
          <h4>6.2.3 请求取消</h4>
          <p>在组件卸载或路由切换时，取消未完成的请求，避免内存泄漏：</p>
          <div class="code-example">
            <pre><code>import { onUnmounted } from 'vue'

export default {
  setup() {
    // 创建取消令牌
    const controller = new AbortController()
    
    async function fetchData() {
      try {
        const response = await get('/api/data', null, {
          signal: controller.signal
        })
        // 处理响应...
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('请求被取消')
        } else {
          console.error('请求错误:', error)
        }
      }
    }
    
    // 组件卸载时取消请求
    onUnmounted(() => {
      controller.abort()
    })
    
    return {
      fetchData
    }
  }
}</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>6.3 安全考虑</h3>
          
          <h4>6.3.1 防止CSRF攻击</h4>
          <p>配置Axios防止CSRF攻击：</p>
          <div class="code-example">
            <pre><code>// 设置默认withCredentials为true，确保发送cookie
axios.defaults.withCredentials = true

// 从cookie中获取CSRF token
function getCSRFToken() {
  const token = document.cookie.match(/XSRF-TOKEN=([^;]+)/)
  return token ? token[1] : ''
}

// 在请求拦截器中添加CSRF token
service.interceptors.request.use(config => {
  config.headers['X-XSRF-TOKEN'] = getCSRFToken()
  return config
})</code></pre>
          </div>
          
          <h4>6.3.2 加密敏感数据</h4>
          <p>对敏感数据进行加密：</p>
          <div class="code-example">
            <pre><code>import CryptoJS from 'crypto-js'

// 加密函数
function encrypt(data, secretKey) {
  return CryptoJS.AES.encrypt(JSON.stringify(data), secretKey).toString()
}

// 在请求拦截器中加密敏感数据
service.interceptors.request.use(config => {
  // 判断是否需要加密
  if (config.needEncrypt && config.data) {
    config.data = {
      data: encrypt(config.data, 'YOUR_SECRET_KEY')
    }
  }
  return config
})</code></pre>
          </div>
        </div>
        
        <div class="subsection">
          <h3>6.4 常见问题与解决方案</h3>
          
          <h4>6.4.1 跨域问题</h4>
          <p>解决跨域问题的常见方法：</p>
          <ol>
            <li><strong>配置代理</strong>：在开发环境中使用Vite/Webpack的代理功能</li>
            <li><strong>后端配置CORS</strong>：在后端服务器配置正确的CORS头</li>
            <li><strong>使用环境变量</strong>：根据不同环境配置不同的baseURL</li>
          </ol>
          <div class="code-example">
            <pre><code>// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://your-api-server.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})</code></pre>
          </div>
          
          <h4>6.4.2 大文件上传</h4>
          <p>处理大文件上传的策略：</p>
          <ol>
            <li><strong>分片上传</strong>：将大文件分成多个小块上传</li>
            <li><strong>断点续传</strong>：支持上传中断后从断点继续</li>
            <li><strong>进度监控</strong>：显示上传进度</li>
          </ol>
          <div class="code-example">
            <pre><code>// 分片上传实现
async function uploadLargeFile(file, url) {
  const chunkSize = 1024 * 1024 * 2 // 2MB一片
  const totalChunks = Math.ceil(file.size / chunkSize)
  
  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize
    const end = Math.min(file.size, start + chunkSize)
    const chunk = file.slice(start, end)
    
    const formData = new FormData()
    formData.append('file', chunk)
    formData.append('fileName', file.name)
    formData.append('chunkIndex', i)
    formData.append('totalChunks', totalChunks)
    
    await post(url, formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
  }
  
  // 通知服务器所有分片上传完成
  return post(`${url}/complete`, {
    fileName: file.name,
    totalChunks
  })
}</code></pre>
          </div>
          
          <h4>6.4.3 并发请求控制</h4>
          <p>控制并发请求数量，避免服务器压力过大：</p>
          <div class="code-example">
            <pre><code>// 并发请求控制
async function concurrentRequests(requests, maxConcurrent = 5) {
  const results = []
  const executing = new Set()
  
  for (const [index, request] of requests.entries()) {
    // 创建Promise
    const p = Promise.resolve().then(() => request())
    results.push(p)
    
    // 如果达到最大并发数，等待一个请求完成
    if (maxConcurrent <= requests.length) {
      executing.add(p)
      p.then(() => executing.delete(p))
      
      if (executing.size >= maxConcurrent) {
        await Promise.race(executing)
      }
    }
  }
  
  return Promise.all(results)
}</code></pre>
          </div>
        </div>
      </section>
      
      <!-- 结语 -->
      <section class="content-section conclusion">
        <h2>总结</h2>
        <p>本教程详细介绍了Axios在Vue项目中的封装与使用，包括：</p>
        <ul>
          <li>Axios基础知识与封装思路</li>
          <li>请求与响应拦截器的设计</li>
          <li>统一的错误处理策略</li>
          <li>实战案例展示</li>
          <li>性能优化与最佳实践</li>
        </ul>
        <p>通过合理封装Axios，我们可以大大提高项目的开发效率、可维护性和扩展性。希望本教程对您的项目开发有所帮助！</p>
      </section>
      
      <!-- 演示组件 -->
      <section class="demo-section">
        <h2>在线演示</h2>
        
        <div v-if="showMessage" :class="['notification', messageType]">
          {{ message }}
        </div>
        
        <!-- 用户管理表单 -->
        <div class="card">
          <h3>添加用户</h3>
          <div class="form">
            <div class="form-group">
              <label>用户名</label>
              <input v-model="newUser.name" type="text" placeholder="请输入用户名" />
            </div>
            
            <div class="form-group">
              <label>邮箱</label>
              <input v-model="newUser.email" type="email" placeholder="请输入邮箱" />
            </div>
            
            <button @click="createUser" class="btn primary" :disabled="loading">
              {{ loading ? '提交中...' : '创建用户' }}
            </button>
          </div>
        </div>
        
        <!-- 用户列表 -->
        <div class="card">
          <h3>用户列表</h3>
          
          <div v-if="loading" class="loading">
            <div class="spinner"></div>
            <p>加载中...</p>
          </div>
          
          <div v-else-if="error" class="error">
            <p>{{ error }}</p>
            <button @click="fetchUsers" class="btn primary">重试</button>
          </div>
          
          <div v-else class="users-list">
            <div v-for="user in users" :key="user.id" class="user-item">
              <div class="user-info">
                <img :src="user.avatar" :alt="user.name" class="avatar" />
                <div>
                  <h4>{{ user.name }}</h4>
                  <p>{{ user.email }}</p>
                </div>
              </div>
              
              <div class="user-actions">
                <button @click="updateUser(user.id, { ...user, name: user.name + ' (已更新)' })" 
                        class="btn secondary small">
                  更新
                </button>
                <button @click="deleteUser(user.id)" class="btn danger small">删除</button>
              </div>
            </div>
          </div>
        </div>
      </section>
    </main>
  </div>
</template>

<style scoped>
.tutorial-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

header {
  margin-bottom: 30px;
  border-bottom: 1px solid #eee;
  padding-bottom: 20px;
}

h1 {
  font-size: 2.5rem;
  margin-bottom: 10px;
}

.subtitle {
  font-size: 1.2rem;
  color: #666;
  margin-bottom: 20px;
}

.toc-section {
  margin-bottom: 40px;
  padding: 20px;
  background-color: #f8f8f8;
  border-radius: 8px;
}

.toc-list {
  padding-left: 20px;
}

.toc-list li {
  margin-bottom: 10px;
}

.toc-list a {
  color: #0066cc;
  text-decoration: none;
}

.toc-list a:hover {
  text-decoration: underline;
}

.content-section {
  margin-bottom: 50px;
}

.subsection {
  margin-bottom: 30px;
}

.code-example {
  background-color: #f8f8f8;
  border-radius: 6px;
  padding: 15px;
  margin: 15px 0;
  overflow-x: auto;
}

pre {
  margin: 0;
}

code {
  font-family: 'Courier New', monospace;
  font-size: 0.9rem;
}

.demo-section {
  margin-top: 50px;
  margin-bottom: 30px;
}

.card {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin-bottom: 20px;
}

.form {
  max-width: 500px;
}

.form-group {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: 500;
}

input {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
}

.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  transition: background-color 0.2s;
}

.btn.primary {
  background-color: #4CAF50;
  color: white;
}

.btn.primary:hover {
  background-color: #3e8e41;
}

.btn.secondary {
  background-color: #f1f1f1;
  color: #333;
}

.btn.secondary:hover {
  background-color: #e0e0e0;
}

.btn.danger {
  background-color: #f44336;
  color: white;
}

.btn.danger:hover {
  background-color: #d32f2f;
}

.btn.small {
  padding: 4px 8px;
  font-size: 0.9rem;
}

.btn:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.notification {
  padding: 10px 15px;
  border-radius: 4px;
  margin-bottom: 20px;
}

.notification.success {
  background-color: #dff0d8;
  color: #3c763d;
  border: 1px solid #d6e9c6;
}

.notification.error {
  background-color: #f2dede;
  color: #a94442;
  border: 1px solid #ebccd1;
}

.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 30px 0;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #4CAF50;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 10px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.error {
  text-align: center;
  padding: 20px;
  color: #a94442;
}

.users-list {
  display: grid;
  gap: 15px;
}

.user-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  border: 1px solid #eee;
  border-radius: 6px;
}

.user-info {
  display: flex;
  align-items: center;
  gap: 15px;
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
}

.user-actions {
  display: flex;
  gap: 10px;
}

.conclusion {
  padding: 20px;
  background-color: #f9f9f9;
  border-radius: 8px;
  border-left: 4px solid #4CAF50;
}
</style> 